/*
 * Copyright 2020 Mamoe Technologies and contributors.
 *
 * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
 * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
 *
 * https://github.com/mamoe/mirai/blob/master/LICENSE
 */

@file:OptIn(MiraiInternalAPI::class, LowLevelAPI::class)
@file:Suppress("EXPERIMENTAL_API_USAGE", "DEPRECATION_ERROR", "NOTHING_TO_INLINE")

package net.mamoe.mirai.qqandroid.contact

import kotlinx.atomicfu.AtomicInt
import kotlinx.atomicfu.atomic
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.io.core.Closeable
import net.mamoe.mirai.LowLevelAPI
import net.mamoe.mirai.contact.Friend
import net.mamoe.mirai.data.FriendInfo
import net.mamoe.mirai.event.broadcast
import net.mamoe.mirai.event.events.BeforeImageUploadEvent
import net.mamoe.mirai.event.events.EventCancelledException
import net.mamoe.mirai.event.events.ImageUploadEvent
import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.isContentNotEmpty
import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.network.highway.postImage
import net.mamoe.mirai.qqandroid.network.highway.sizeToString
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Cmd0x352
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image.LongConn
import net.mamoe.mirai.qqandroid.utils.MiraiPlatformUtils
import net.mamoe.mirai.qqandroid.utils.toUHexString
import net.mamoe.mirai.utils.*
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
import kotlin.coroutines.CoroutineContext
import kotlin.jvm.JvmSynthetic
import kotlin.math.roundToInt
import kotlin.time.ExperimentalTime
import kotlin.time.measureTime

internal inline class FriendInfoImpl(
    private val jceFriendInfo: net.mamoe.mirai.qqandroid.network.protocol.data.jce.FriendInfo
) : FriendInfo {
    override val nick: String get() = jceFriendInfo.nick
    override val uin: Long get() = jceFriendInfo.friendUin
}

@OptIn(ExperimentalContracts::class)
internal inline fun Friend.checkIsFriendImpl(): FriendImpl {
    contract {
        returns() implies (this@checkIsFriendImpl is FriendImpl)
    }
    check(this is FriendImpl) { "A Friend instance is not instance of FriendImpl. Don't interlace two protocol implementations together!" }
    return this
}

internal class FriendImpl(
    bot: QQAndroidBot,
    coroutineContext: CoroutineContext,
    override val id: Long,
    private val friendInfo: FriendInfo
) : Friend() {
    override val coroutineContext: CoroutineContext = coroutineContext + SupervisorJob(coroutineContext[Job])

    @Suppress("unused") // bug
    val lastMessageSequence: AtomicInt = atomic(-1)

    override val bot: QQAndroidBot by bot.unsafeWeakRef()
    override val nick: String
        get() = friendInfo.nick

    @JvmSynthetic
    @Suppress("DuplicatedCode")
    override suspend fun sendMessage(message: Message): MessageReceipt<Friend> {
        require(message.isContentNotEmpty()) { "message is empty" }
        return sendMessageImpl(this, message).also {
            logMessageSent(message)
        }
    }

    @JvmSynthetic
    @OptIn(MiraiInternalAPI::class, ExperimentalStdlibApi::class, ExperimentalTime::class)
    override suspend fun uploadImage(image: ExternalImage): Image = try {
        @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
        if (image.input is net.mamoe.mirai.utils.internal.DeferredReusableInput) {
            image.input.init(bot.configuration.fileCacheStrategy)
        }
        if (BeforeImageUploadEvent(this, image).broadcast().isCancelled) {
            throw EventCancelledException("cancelled by BeforeImageUploadEvent.ToGroup")
        }
        bot.network.run {
            val response = LongConn.OffPicUp(
                bot.client, Cmd0x352.TryUpImgReq(
                    srcUin = bot.id.toInt(),
                    dstUin = id.toInt(),
                    fileId = 0,
                    fileMd5 = @Suppress("INVISIBLE_MEMBER") image.md5,
                    fileSize = @Suppress("INVISIBLE_MEMBER")
                    image.input.size.toInt(),
                    fileName = @Suppress("INVISIBLE_MEMBER") image.md5.toUHexString("") + "." + ExternalImage.defaultFormatName,
                    imgOriginal = 1
                )
            ).sendAndExpect<LongConn.OffPicUp.Response>()

            @Suppress("UNCHECKED_CAST", "DEPRECATION", "INVISIBLE_MEMBER")
            return when (response) {
                is LongConn.OffPicUp.Response.FileExists -> net.mamoe.mirai.message.data.OfflineFriendImage(response.resourceId)
                    .also {
                        ImageUploadEvent.Succeed(this@FriendImpl, image, it).broadcast()
                    }
                is LongConn.OffPicUp.Response.RequireUpload -> {
                    bot.network.logger.verbose {
                        "[Http] Uploading friend image, size=${image.input.size.sizeToString()}"
                    }

                    val time = measureTime {
                        MiraiPlatformUtils.Http.postImage(
                            "0x6ff0070",
                            bot.id,
                            null,
                            imageInput = image.input,
                            uKeyHex = response.uKey.toUHexString("")
                        )
                    }

                    bot.network.logger.verbose {
                        "[Http] Uploading friend image: succeed at ${(image.input.size.toDouble() / 1024 / time.inSeconds).roundToInt()} KiB/s"
                    }

                    /*
                    HighwayHelper.uploadImageToServers(
                        bot,
                        response.serverIp.zip(response.serverPort),
                        response.uKey,
                        image,
                        kind = "friend",
                        commandId = 1
                    )*/
                    // 为什么不能 ??

                    return net.mamoe.mirai.message.data.OfflineFriendImage(response.resourceId).also {
                        ImageUploadEvent.Succeed(this@FriendImpl, image, it).broadcast()
                    }
                }
                is LongConn.OffPicUp.Response.Failed -> {
                    ImageUploadEvent.Failed(this@FriendImpl, image, -1, response.message).broadcast()
                    error(response.message)
                }
            }
        }
    } finally {
        @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
        (image.input as? Closeable)?.close()
    }
}